﻿/* Copyright 2010 Igor Francé
 * 
 * This file is part of ECMADoc (http://ecmadoc.codeplex.com).
 * 
 * ECMADoc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 
 * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * ECMADoc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with ECMADoc. 
 * If not, see http://www.gnu.org/licenses/.
 */
namespace EcmaDoc.Core.Items
{
	using System;
	using System.Collections.Generic;
	using System.Xml;

	using Configuration;

	/// <summary>
	/// Represents a function code item.
	/// </summary>
	public class FunctionItem : ObjectItem
	{
		private string arguments;

		/// <summary>
		/// Initializes a new instance of the <see cref="FunctionItem"/> class, using the specified
		/// <paramref name="parentItem"/> and <paramref name="name"/>.
		/// </summary>
		/// <param name="parentItem">The parent item that this code item belongs to.</param>
		/// <param name="name">The name of this function item.</param>
		public FunctionItem(CodeItem parentItem, string name)
			: this(parentItem, name, (Comment) null)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="FunctionItem"/> class, using the specified
		/// <paramref name="parentItem"/>, <paramref name="name"/> and <paramref name="commentString"/>.
		/// </summary>
		/// <param name="parentItem">The parent item that this code item belongs to.</param>
		/// <param name="name">The name of this function item.</param>
		/// <param name="commentString">The comment string for this function item.</param>
		public FunctionItem(CodeItem parentItem, string name, string commentString)
			: this(parentItem, name, (Comment) null)
		{
			this.Comment = Comment.Create(commentString, this);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="FunctionItem"/> class, using the specified
		/// <paramref name="parentItem"/>, <paramref name="name"/> and <paramref name="comment"/>.
		/// </summary>
		/// <param name="parentItem">The parent item that this code item belongs to.</param>
		/// <param name="name">The name of this function item.</param>
		/// <param name="comment">The parsed comment for this function item.</param>
		public FunctionItem(CodeItem parentItem, string name, Comment comment)
			: base(parentItem, name, comment)
		{
			ItemType = ItemType.Function;
			Arguments = new OrderedDictionary<string, Argument>();
			DocumentedArguments = new OrderedDictionary<string, Argument>();
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="FunctionItem"/> class, using the specified
		/// <paramref name="parentItem"/>, <paramref name="name"/>, <paramref name="commentString"/> and <paramref name="arguments"/>.
		/// </summary>
		/// <param name="parentItem">The parent item that this code item belongs to.</param>
		/// <param name="name">The name of this function item.</param>
		/// <param name="commentString">The comment string for this function item.</param>
		/// <param name="arguments">The argument string of this function item.</param>
		public FunctionItem(CodeItem parentItem, string name, string commentString, string arguments) 
			: this(parentItem, name, commentString)
		{
			this.ArgString = arguments;
		}

		/// <summary>
		/// Gets this function's arguments.
		/// </summary>
		public OrderedDictionary<string, Argument> Arguments
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets this function's documented arguments.
		/// </summary>
		public OrderedDictionary<string, Argument> DocumentedArguments
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets the type of this item
		/// </summary>
		/// <value></value>
		public override ItemType ItemType
		{
			get
			{
				if (MemberGroups[Visibility.Prototype].Count != 0)
					return ItemType.Class;

				if (MemberGroups[Visibility.Instance].Count != 0)
					return ItemType.Class;

				return base.ItemType;
			}

			internal set
			{
				base.ItemType = value;
			}
		}

		/// <summary>
		/// Gets the <see cref="ReturnItem"/> associated with this function.
		/// </summary>
		public ReturnItem Returns
		{
			get;
			internal set;
		}

		internal CodeItem ReturnItem { get; set; }

		/// <summary>
		/// Gets a value indicating whether this instance is static.
		/// </summary>
		/// <value><c>true</c> if this instance is static; otherwise, <c>false</c>.</value>
		internal bool IsStatic
		{
			get
			{
				int staticCount = MemberGroups[Visibility.Static].Count;
				if (staticCount != 0 && staticCount == MemberCount)
					return true;

				return false;
			}
		}

		/// <summary>
		/// Gets or sets the argument string associated with this function.
		/// </summary>
		/// <value>The argument string associated with this function.</value>
		internal string ArgString
		{
			get
			{
				return arguments;
			}

			set
			{
				Arguments = new OrderedDictionary<string, Argument>();
				arguments = value;
				if (!string.IsNullOrEmpty(arguments))
				{
					string[] args = arguments.Split(',');
					for (int i = 0; i < args.Length; i++)
					{
						string argName = args[i].Trim();
						this.Arguments.Add(argName, new Argument(argName));
					}
				}
			}
		}

		/// <summary>
		/// Gets a value indicating whether this function uses variable number of arguments.
		/// </summary>
		internal bool VariableArguments { get; private set; }

		/// <summary>
		/// Gets the type of the variable arguments of this function.
		/// </summary>
		internal string VariableArgumentsType { get; private set; }

		/// <summary>
		/// Gets or sets the type url of the variable arguments of this function.
		/// </summary>
		internal string VariableArgumentsTypeUrl { get; set; }

		/// <summary>
		/// Gets the range (string [1-n]) of the variable arguments of this function.
		/// </summary>
		internal string VariableArgumentsRange { get; private set; }

		/// <summary>
		/// Gets or sets the description of the variable arguments of this function.
		/// </summary>
		internal string VariableArgumentsDescription { get; set; }

		/// <summary>
		/// Sets the variable arguments of this function.
		/// </summary>
		/// <param name="type">The type associated with the variable arguments.</param>
		/// <param name="range">The range associated with the variable arguments.</param>
		/// <param name="description">The description associated with the variable arguments.</param>
		public void SetVariableArguments(string type, string range, string description)
		{
			VariableArgumentsType = type;
			VariableArgumentsRange = range;
			VariableArgumentsDescription = description;
			VariableArguments = true;
		}

		/// <summary>
		/// Gets the name of the xml node that represents this code item.
		/// </summary>
		/// <returns>
		/// The <see cref="ItemType"/> string converted to lower case.
		/// </returns>
		internal override string GetNodeName()
		{
			if (this.ItemType == ItemType.Class)
			{
				return "class";
			}

			if (Parent.ItemType != ItemType.Namespace)
				return "method";

			return "function";
		}

		/// <summary>
		/// Creates an XML node that represents this item.
		/// </summary>
		/// <param name="document">The owner document to use for creating the node.</param>
		/// <param name="processChildren">If set to <c>true</c>, the child members will be appended as xml elements too.</param>
		/// <returns>An XML node that represents this item.</returns>
		internal override XmlElement ToXml(XmlDocument document, bool processChildren)
		{
			XmlElement self = base.ToXml(document, processChildren);

			if (IsStatic)
				self.SetAttribute("static", "true");

			OrderedDictionary<string, Argument> args = DocumentedArguments.Count != 0 
				? DocumentedArguments
				: Arguments;

			if (args.Count != 0 || VariableArguments)
			{
				XmlElement argsNode = (XmlElement) self.InsertBefore(document.CreateElement("arguments"), self.ChildNodes[0]);
				if (args.Count != 0)
				{
					argsNode.SetAttribute("value", this.ArgString);
					foreach (Argument arg in args.Values)
					{
						argsNode.AppendChild(arg.ToXml(document));
					}
				}

				if (VariableArguments)
				{
					XmlElement varNode = (XmlElement) argsNode.AppendChild(document.CreateElement("variable"));
					varNode.SetAttribute("type", VariableArgumentsType);
					varNode.SetAttribute("range", VariableArgumentsRange);
					if (VariableArgumentsTypeUrl != null)
						varNode.SetAttribute("typeUrl", VariableArgumentsTypeUrl);

					varNode.SetAttribute("description", VariableArgumentsDescription);
				}
			}
			if (Returns != null)
				self.AppendChild(Returns.ToXml(document));

			return self;
		}

		/// <summary>
		/// Unserializes the current code item from the specified item node.
		/// </summary>
		/// <param name="itemNode">The item node that contains the previously serialized code item to
		/// restore into the current code item.</param>
		protected internal override void Unserialize(XmlElement itemNode)
		{
			base.Unserialize(itemNode);

			XmlAttribute attrNode = itemNode.SelectSingleNode("arguments/@value") as XmlAttribute;
			if (attrNode != null)
			{
				ArgString = attrNode.Value;
			}
		}

		/// <summary>
		/// Gets a string that specified a short example of this code item's usage.
		/// </summary>
		/// <param name="extended">If <c>true</c>, the resulting string will be HTML with links to other types.</param>
		/// <returns>
		/// A string that specified a short example of this code item's usage
		/// </returns>
		protected internal override string GetSynopsis(bool extended)
		{
			if (this.ItemType == ItemType.Class && !this.IsStatic)
				return this.GetConstructorSynopsis(extended);

			return GetFunctionSynopsis(extended, false);
		}

		private string GetFunctionSynopsis(bool extended, bool baseStringOnly)
		{
			string returnsType = ParserConfig.DefaultVoidType;
			
			if (this.Returns != null)
			{
				if (this.Returns.TypeUrl != null && extended)
					returnsType = Config.MakeLink(this.Returns.TypeUrl, this.Returns.Type);
				else
					returnsType = this.Returns.Type;
			}

			returnsType = string.Format(Config.ReturnValueFormat, returnsType);

			OrderedDictionary<string, Argument> args = DocumentedArguments.Count != 0
				? DocumentedArguments
				: Arguments;

			List<string> outputArgs = new List<string>();
			if (args.Count != 0)
			{
				foreach (Argument a in args.Values)
				{
					if (extended && a.TypeUrl != null)
						outputArgs.Add(string.Format(Config.ArgumentFormat, a.Name, Config.MakeLink(a.TypeUrl, a.Type)));
					else
						outputArgs.Add(string.Format(Config.ArgumentFormat, a.Name, a.Type));
				}
			}

			var itemName = this.Visibility == Visibility.Static
				? this.FullName
				: this.Name;

			if (baseStringOnly)
				return string.Format(Config.FunctionSynopsisBase, itemName, outputArgs.Join(Config.CommaFormat));

			return string.Format(Config.FunctionSynopsisFull, itemName, outputArgs.Join(Config.CommaFormat), returnsType);
		}

		private string GetConstructorSynopsis(bool extended)
		{
			string functionSynopsis = GetFunctionSynopsis(extended, true);

			string type = extended
				? Config.MakeLink(this.Url, this.Name)
				: this.Name;

			return string.Format(Config.ConstructorSynopsis, type, functionSynopsis);
		}
	}
}
